package logs

import (
	"encoding/json"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"runtime"
	"strconv"
	"strings"

	"gitee.com/haodreams/libs/easy"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	"gopkg.in/natefinch/lumberjack.v2"
)

var log *zap.Logger
var sugar *zap.SugaredLogger

// var config zapcore.EncoderConfig
var level *zap.AtomicLevel
var appName string

// 常量定义
const (
	// LevelDebug defines debug log level.
	LevelDebug = zap.DebugLevel
	// LevelInfo defines info log level.
	LevelInfo = zap.InfoLevel
	// LevelWarn defines warn log level.
	LevelWarn = zap.WarnLevel
	// LevelError defines error log level.
	LevelError = zap.WarnLevel

	AdapterConsole = "console"
	AdapterFile    = "file"
)

var multi = new(multiWriter)

func init() {
	binaryPath, _ := filepath.Abs(os.Args[0])

	name := filepath.Base(binaryPath)
	appName = strings.TrimSuffix(name, filepath.Ext(name))

	//默认的配置
	log, _ = zap.NewProduction()
	// sugar = log.Sugar()

	config := zap.NewProductionEncoderConfig()
	config.EncodeTime = zapcore.TimeEncoderOfLayout(easy.YYYYMMDDHHMMSSMill)
	config.TimeKey = "time"
	config.MessageKey = "message"
	var encoder = zapcore.NewConsoleEncoder(config)
	level = new(zap.AtomicLevel)
	*level = zap.NewAtomicLevelAt(LevelDebug)
	core := zapcore.NewCore(encoder, os.Stderr, level)
	log = zap.New(core)
	sugar = log.Sugar()
}

type multiWriter struct {
	writers []zapcore.WriteSyncer
}

func (m *multiWriter) Sync() error {
	// for _, w := range m.writers {
	// 	w.Sync()
	// }
	return nil
}

/**
 * @description:
 * @param {*}
 * @return {*}
 */
func GetWriter() io.Writer {
	return multi
}

func (m *multiWriter) Write(p []byte) (n int, err error) {
	for _, w := range m.writers {
		n, err = w.Write(p)
		if err != nil {
			return
		}
		if n != len(p) {
			err = io.ErrShortWrite
			return
		}
	}
	return len(p), nil
}

/**
 * @description: 获取app的名称
 * @param {*}
 * @return {*}
 */
func GetAppName() string {
	return appName
}

func Exit(msg string, exitCode ...int) {
	defer func() {
		code := 255
		if len(exitCode) > 0 {
			code = exitCode[0]
		}
		os.Exit(code)
	}()
	if sugar == nil {
		fmt.Println(msg)
	} else {
		Warn(msg)
	}
	f, err := os.OpenFile(appName+".log", os.O_RDWR|os.O_CREATE, 0666)
	if err != nil {
		fmt.Println("打开日志文件错误")
		return
	}
	defer f.Close()
	f.WriteString(msg)
	f.WriteString("\n")
}

/**
 * @description: 日志初始化
 * @param {string} logType 日志类型 json/txt
 * @param {bool} haveColor 日志是否输出颜色
 * @return {*}
 */
func Init(logType string, haveColor bool) {
	config := zap.NewProductionEncoderConfig()
	config.EncodeTime = zapcore.TimeEncoderOfLayout(easy.YYYYMMDDHHMMSSMill)
	if haveColor {
		config.EncodeLevel = zapcore.CapitalColorLevelEncoder
	}
	config.TimeKey = "time"
	config.MessageKey = "message"
	var encoder zapcore.Encoder
	if logType == "json" {
		encoder = zapcore.NewJSONEncoder(config)
	} else {
		encoder = zapcore.NewConsoleEncoder(config)
	}
	level = new(zap.AtomicLevel)
	*level = zap.NewAtomicLevelAt(LevelInfo)
	core := zapcore.NewCore(encoder, multi, level)
	log = zap.New(core)
	sugar = log.Sugar()
}

// SetLevel sets the global log level used by the simple logger.
func SetLevel(l zapcore.Level) {
	level.SetLevel(l)
	level.Level()
}

func Level() int {
	return int(level.Level())
}

/**
 * @description: 用字符串的方式设置日志级别
 * @param {string} l
 * @return {*}
 */
func SetStringLevel(l string) {
	l = strings.ToLower(l)
	switch l {
	case "error":
		SetLevel(LevelError)
	case "warn", "warning":
		SetLevel(LevelWarn)
	case "info", "informational":
		SetLevel(LevelInfo)
	case "debug":
		SetLevel(LevelDebug)
	}
}

// SetLogFuncCall set the CallDepth, default is 4
// func SetLogFuncCall(b bool) {
// 	//logger = logger.Hook(ch)
// }

// SetLogFuncCallDepth set log funcCallDepth
// func SetLogFuncCallDepth(d int) {
// 	//ch.callerSkipFrameCount = d + 1
// }

// SetLogger sets a new logger.
// 每种类型只能调用一次
func SetLogger(adapter string, config ...string) error {
	switch adapter {
	case AdapterConsole:
		multi.writers = append(multi.writers, os.Stdout)
	case AdapterFile:
		l := new(lumberjack.Logger)
		l.LocalTime = true
		if len(config) > 0 {
			err := json.Unmarshal([]byte(config[0]), l)
			if err != nil {
				fmt.Println(err)
			}
		}
		if l.MaxAge <= 0 {
			l.MaxAge = 30
		}

		l.MaxSize /= 1024 * 1024
		if l.MaxSize <= 0 {
			l.MaxSize = 1
		}
		if l.MaxSize > 50 {
			l.MaxSize = 50
		}

		multi.writers = append(multi.writers, zapcore.AddSync(l))
	default:
		return fmt.Errorf("%s 不支持的log 记录器", adapter)
	}
	return nil
}

// AddWriter .
func AddWriter(w io.Writer) {
	multi.writers = append(multi.writers, zapcore.AddSync(w))
}

// Errorf logs a message at error level.
func Errorf(f string, v ...interface{}) {
	sugar.Errorw(formatLogf(Where(2)+f, v...))
}

// Warnf compatibility alias for Warning()
func Warnf(f string, v ...interface{}) {
	sugar.Warnw(formatLogf(Where(2)+f, v...))
}

// Infof compatibility alias for Warning()
func Infof(f string, v ...interface{}) {
	sugar.Infow(formatLogf(Where(2)+f, v...))
}

// Debugf logs a message at debug level.
func Debugf(f string, v ...interface{}) {
	sugar.Debugw(formatLogf(Where(2)+f, v...))
}

// Error logs a message at error level.
func Error(v ...interface{}) {
	sugar.Errorw(Where(2) + formatLog(v...))
}

// Warn compatibility alias for Warning()
func Warn(v ...interface{}) {
	sugar.Warnw(Where(2) + formatLog(v...))
}

// Info compatibility alias for Warning()
func Info(v ...interface{}) {
	sugar.Infow(Where(2) + formatLog(v...))
}

// Debug logs a message at debug level.
func Debug(v ...interface{}) {
	sugar.Debugw(Where(2) + formatLog(v...))
}

// Error logs a message at error level.
func LiteError(s string, v ...interface{}) {
	sugar.Errorw(s + formatLog(v...))
}

// Warn compatibility alias for Warning()
func LiteWarn(s string, v ...interface{}) {
	sugar.Warnw(s + formatLog(v...))
}

// Info compatibility alias for Warning()
func LiteInfo(s string, v ...interface{}) {
	sugar.Infow(s + formatLog(v...))
}

// Debug logs a message at debug level.
func LiteDebug(s string, v ...interface{}) {
	sugar.Debugw(s + formatLog(v...))
}

// Info compatibility alias for Warning()
func Msg(msg string, level ...int) {
	if len(level) == 0 {
		sugar.Infow(msg)
		return
	}

	switch level[0] {
	case 0:
		sugar.Errorw(msg)
	case 1:
		sugar.Warnw(msg)
	case 3:
		sugar.Debugw(msg)
	default:
		sugar.Infow(msg)
	}
}

// CbError logs a message at error level.
func CbError(v ...interface{}) {
	sugar.Errorw(Where(3) + formatLog(v...))
}

// CbWarn compatibility alias for Warning()
func CbWarn(v ...interface{}) {
	sugar.Warnw(Where(3) + formatLog(v...))
}

// CbInfo compatibility alias for Warning()
func CbInfo(v ...interface{}) {
	sugar.Infow(Where(3) + formatLog(v...))
}

// CbDebug logs a message at debug level.
func CbDebug(v ...interface{}) {
	sugar.Debugw(Where(3) + formatLog(v...))
}

// CbErrorf logs a message at error level.
func CbErrorf(format string, v ...interface{}) {
	sugar.Errorw(formatLogf(Where(3)+format, v...))
}

// CbWarnf compatibility alias for Warning()
func CbWarnf(format string, v ...interface{}) {
	sugar.Warnw(formatLogf(Where(3)+format, v...))
}

// CbInfof compatibility alias for Warning()
func CbInfof(format string, v ...interface{}) {
	sugar.Infow(formatLogf(Where(3)+format, v...))
}

// CbDebugf logs a message at debug level.
func CbDebugf(format string, v ...interface{}) {
	sugar.Debugw(formatLogf(Where(3)+format, v...))
}

// Where .
func Where(l int) string {
	frame, ok := GetCallerFrame(l)
	if ok {
		return TrimmedPath(frame) + " "
	}
	return "??? "
}

func formatLogf(msg string, v ...interface{}) string {
	if strings.Contains(msg, "%") && !strings.Contains(msg, "%%") {
		//format string
	} else {
		//do not contain format char
		msg += strings.Repeat(" %v", len(v))
	}
	return fmt.Sprintf(msg, v...)
}

func formatLog(v ...interface{}) string {
	msg := fmt.Sprintln(v...)
	n := len(msg)
	if n > 0 {
		msg = msg[:n-1]
	}
	return msg
}

func GetCallerFrame(skip int) (frame *runtime.Frame, ok bool) {
	const skipOffset = 2 // skip getCallerFrame and Callers

	pc := make([]uintptr, 1)
	numFrames := runtime.Callers(skip+skipOffset, pc)
	if numFrames < 1 {
		return
	}

	frame1, _ := runtime.CallersFrames(pc).Next()
	frame = &frame1
	return frame, frame.PC != 0
}

func TrimmedPath(frame *runtime.Frame) string {
	idx := strings.LastIndexByte(frame.File, '/')
	if idx == -1 {
		return frame.File + ":" + strconv.Itoa(frame.Line)
	}
	// Find the penultimate separator.
	idx = strings.LastIndexByte(frame.File[:idx], '/')
	if idx == -1 {
		return frame.File + ":" + strconv.Itoa(frame.Line)
	}

	return frame.File[idx+1:] + ":" + strconv.Itoa(frame.Line)
}
